<?php

declare (strict_types=1);

namespace app\common\service\user;

use app\common\exception\AdminException;
use app\common\repository\ad\RewardLogRepository;
use app\common\repository\user\UserRepository;
use app\common\service\ad\RewardLogService;
use app\common\service\BaseService;
use app\common\service\SmsService;
use app\common\util\JwtUtil;
use think\facade\Db;
use think\facade\Log;
use think\facade\Request;
use Zhuzhichao\IpLocationZh\Ip;
use GuzzleHttp\Client;

class UserService extends BaseService
{
    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
        parent::__construct($repository);
    }

    /**
     * 获取用户列表
     *
     * @param array $params
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     */
    public function getList(array $params = []): array
    {
        [$page, $pageSize] = $this->getPaginationParams();
        $where = [];
        if (isset($params['username'])) {
            $where[] = ['username', 'like', '%' . $params['username'] . '%'];
        }
        if (isset($params['status']) && $params['status'] != -1) {
            $where[] = ['status', '=', $params['status']];
        }
        $data = $this->repository->paginate($where, ['id' => 'desc'], $page, $pageSize);

        foreach($data['list'] as &$vo){
            $vo['day_reward_count'] = Db::name('reward_log')->where('user_id', $vo['id'])->whereDay('create_time')->count();
            $vo['day_task_number'] = (int)get_config('app.day_task_number');
        }
        unset($vo);
        return $data;
    }

    /**
     * 获取用户详情信息
     *
     * @param $id
     * @return \app\common\model\BaseModel|array|mixed
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function getInfo($id): mixed
    {
        return $this->repository->getOne($id);
    }

    /**
     * 创建用户信息
     *
     * @param array $data
     * @return \think\Collection|\app\common\model\BaseModel|array
     * @throws \Exception
     */
    public function create(array $data): \think\Collection|\app\common\model\BaseModel|array
    {
        if (!empty($params['password'])) {
            $data['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
        }
        return $this->repository->save($data);
    }

    /**
     * 更新用户信息
     *
     * @param array $data
     * @param $id
     * @return \app\common\model\BaseModel|bool|int
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function update(array $data, $id)
    {
        if (empty($id)) {
            throw new AdminException('ID 不能为空');
        }
        if (!empty($data['password'])) {
            $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
        }
        return $this->repository->update($data, $id);
    }

    /**
     * 删除用户信息
     *
     * @param $id
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function delete($id): bool
    {
        return $this->repository->delete($id);
    }

    /**
     * 修改用户金币
     *
     * @param $user_id
     * @param $data
     * @return \app\common\model\BaseModel|bool|int
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function saveUserBalance($user_id, $data): \app\common\model\BaseModel|bool|int
    {
        $type = $data['type'];
        $number = (int)$data['number'];
        $userInfo = $this->repository->getOne($user_id);
        if($userInfo['balance'] < $number && $type === 'dec'){
            throw new AdminException('修改金币失败：当前用户金币不能少于扣减金币数量');
        }
        if($type === 'empty') {
            $result = $this->repository->update(['balance' => 0], $user_id);
        } else {
            $result = $this->repository->setInc($user_id, 'balance', $type, $number);
        }
        return $result;
    }

    /**
     * 用户登录
     *
     * @param $params
     * @return string
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function login($params): string
    {
        // 验证码验证
        if ($params['code'] !== '123456') {
            throw new AdminException('验证码错误', 422);
        }

        $userInfo = $this->repository->getOne(['phone' => $params['phone']]);

        // 用户不否存在则注册
        if (!$userInfo) {
            return $this->register($params['phone']);
        } else {
            $userInfo['ip'] = Request::ip();
            $this->repository->update(['ip' => $userInfo['ip']], $userInfo['id']);
        }

        // 生成token
        return JwtUtil::generateToken($userInfo->id, 'user');
    }

    /**
     * 用户注册
     *
     * @param $phone
     * @return string
     * @throws \Exception
     */
    public function register($phone): string
    {
        $data['phone'] = $phone;
        $data['status'] = 0;
        $data['ip'] = Request::ip();
        $userInfo = $this->repository->save($data);
        if ($userInfo) {
            // 成功注册返回用户token
            return JwtUtil::generateToken($userInfo->id, 'user');
        } else {
            throw new AdminException('注册失败，请重试');
        }
    }

    /**
     * 发送验证码
     *
     * @param $phone
     * @return array|string
     */
    public function sendCode($phone): array|string
    {
        $smsService = app()->make(SmsService::class);
        return $smsService->send($phone);
    }

    /**
     * 微信授权登录
     *
     * @param $params
     * @return string 返回 JWT Token
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function wechatLogin($params)
    {
        $appId = get_config('wechat.appid');
        $appSecret = get_config('wechat.appsecret');

        $lng = $params['lng'] ?? null;
        $lat = $params['lat'] ?? null;
        $code = $params['code'];
        if (!$code) {
            throw new AdminException('缺少 code', 400);
        }

        // 创建 Guzzle 客户端
        $client = new Client();

        try {
            $response = $client->get('https://api.weixin.qq.com/sns/oauth2/access_token', [
                'query' => [
                    'appid' => $appId,
                    'secret' => $appSecret,
                    'code' => $code,
                    'grant_type' => 'authorization_code'
                ]
            ]);

            $data = json_decode($response->getBody()->getContents(), true);

            if (isset($data['errcode'])) {
                throw new AdminException($data['errmsg'], 400);
            }

            $openid = $data['openid'];
            $access_token = $data['access_token'];

            // 2. 使用 access_token + openid 获取用户详细信息
            $userInfo = $this->getWechatUserInfoDetail($access_token, $openid);
            if (!$userInfo || isset($userInfo['errcode'])) {
                throw new AdminException('获取用户信息失败', 400);
            }

            // 提取微信用户数据
            $wechatOpenId = $userInfo['openid'];
            $nickname = $userInfo['nickname'] ?? '';
            $avatar = $userInfo['headimgurl'] ?? ''; // 注意：微信返回是 headimgurl
            $sex = $userInfo['sex'] ?? -1; // 1男 2女 0未知

            $ip = Request::ip();

            $res = $this->repository->getOne(['open_id' => $openid]);
            // 新用户插入记录
            if (!$res) {
                $res = $this->repository->save([
                    'open_id' => $wechatOpenId,
                    'username' => $this->filterNickname($nickname),
                    'avatar' => $avatar,
                    'sex' => $sex,
                    'register_ip' => $ip,
                    'lng' => $lng,
                    'lat' => $lat
                ]);
            } else {
                $this->repository->update([
                    'username' => $this->filterNickname($nickname),
                    'avatar' => $avatar,
                    'register_ip' => $ip,
                    'update_time' => time()
                ], $res['id']);
            }

            // 生成JWT token
            return JwtUtil::generateToken($res->id);

        } catch (\Exception $e) {
            throw new AdminException('请求微信接口失败' . $e->getMessage(), 500);
        }
    }

    /**
     * 获取用户微信信息
     *
     * @param $accessToken
     * @param $openid
     * @return mixed|null
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    protected function getWechatUserInfoDetail($accessToken, $openid): mixed
    {
        $client = new Client();
        $url = 'https://api.weixin.qq.com/sns/userinfo';

        try {
            $response = $client->get($url, [
                'query' => [
                    'access_token' => $accessToken,
                    'openid' => $openid,
                    'lang' => 'zh_CN'
                ]
            ]);

            return json_decode($response->getBody()->getContents(), true);
        } catch (\Exception $e) {
            \think\facade\Log::error('获取微信用户详情失败: ' . $e->getMessage());
            return null;
        }
    }

    /**
     * 过滤昵称中的特殊字符（如 emoji）
     * 可根据需要增强
     */
    protected function filterNickname($nickname): string
    {
        // 移除 emoji 或其他非法字符（可选）
        $nickname = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $nickname); // 移除高 Unicode 字符（emoji）
        $nickname = trim($nickname);
        return $nickname ?: '用户' . rand(1000, 9999);
    }

    /**
     * 激励回调
     *
     * @param $data
     * @return \think\response\Json
     */
    public function adCallback($data)
    {
        try {
            $rewardLogRep = app()->make(RewardLogRepository::class);
            Log::info('TakuAd 回调原始数据:' . json_encode($data));
            // 1. 验证必要字段
            $required = ['user_id', 'trans_id', 'reward_name', 'reward_amount', 'placement_id', 'network_firm_id', 'sign'];
            foreach ($required as $key) {
                if (!isset($data[$key]) || $data[$key] === '') {
                    Log::error("缺少必要参数: {$key}");
                    return json(['code' => 400, 'msg' => '参数缺失']);
                }
            }

            // 2. 验证签名
            if (!$this->verifySign($data, $data['platform'])) {
                Log::error('签名验证失败，可能为伪造请求');
                return json(['code' => 400, 'msg' => '非法请求']);
            }

            // 3. 防重：trans_id 是否已处理
            $transId = $data['trans_id'];
            $exists = $rewardLogRep->getOne(['trans_id' => $transId]);
            if ($exists) {
                Log::warning("trans_id 已处理过，防止重复发放: {$transId}");
                return json(['code' => 0, 'msg' => 'success']);
            }

            // 4. 判断是否只处理自有流量（Adx/直投/交叉推广）
            $networkFirmId = (int)$data['network_firm_id'];
            $validFirmIds = [35, 66, 67]; // 你自己的流量源
//            if (!in_array($networkFirmId, $validFirmIds)) {
//                Log::info("忽略第三方广告平台回调，network_firm_id: {$networkFirmId}");
//                return json(['code' => 0, 'msg' => 'success']);
//            }

            // 5. 提取基础字段
            $userId = $data['user_id'];
            $extraData = $data['extra_data'] ?? null;
            $rewardName = $data['reward_name'];
            $rewardAmount = (int)$data['reward_amount'];
            $placementId = $data['placement_id'];
            $platform = $data['platform'] ?? null;        // 1=Android, 2=iOS
            $packageName = $data['package_name'] ?? '';
            $scenarioId = $data['scenario_id'] ?? '';
            $rvPlayDur = $data['rv_play_dur'] ?? ''; // 播放时长

            // 6. 解析 ilrd 字段（关键：收益信息）
            $ilrd = [];
            if (!empty($data['ilrd'])) {
                $ilrd = json_decode($data['ilrd'], true);
                if (json_last_error() !== JSON_ERROR_NONE) {
                    Log::warning("ilrd 解析失败: " . json_last_error_msg());
                }
            }

            // 7. 查询用户
            $user = $this->repository->getOne($userId);
            if (!$user) {
                Log::error("用户不存在: user_id={$userId}");
                throw new AdminException('用户不存在', 500);
            }

            $this->transaction(function () use ($ilrd, $rvPlayDur, $rewardLogRep, $user, $rewardAmount, $rewardName, $extraData, $userId, $transId, $placementId, $networkFirmId, $platform, $packageName, $scenarioId) {
                $adsource_price = round($ilrd['adsource_price'], 4);
                // 9. 构造日志数据（含 ilrd 收益信息）
                $logData = [
                    'user_id' => $userId,
                    'trans_id' => $transId,
                    'ad_platform' => 'taku',
                    'reward_name' => $rewardName,
                    'reward_amount' => $rewardAmount,
                    'placement_id' => $placementId,
                    'network_firm_id' => $networkFirmId,
                    'extra_data' => $extraData,
                    'platform' => $platform,
                    'package_name' => $packageName,
                    'scenario_id' => $scenarioId,
                    'rv_play_dur' => $rvPlayDur,

                    // 从 ilrd 提取收益
                    'publisher_revenue_usd' => $ilrd['publisher_revenue'] ?? 0,
                    'publisher_revenue_cny' => $ilrd['publisher_revenue_cny'] ?? 0,
//                    'adsource_price_cny' => $ilrd['adsource_price_cny'] ?? 0,
//                    'ecpm' => ($ilrd['adsource_price_cny'] ?? 0) / 1000, // eCPM = 千次展示收益
//                    'coins' => $ilrd['adsource_price_cny'] ?? 0, // 金币
                    'adsource_price_cny' => $adsource_price,
                    'ecpm' => $adsource_price / 1000, // eCPM = 千次展示收益
                    'coins' => $adsource_price, // 金币
                    'adunit_format' => $ilrd['adunit_format'] ?? null,
                    'network_name' => $ilrd['network_name'] ?? null,
                    'country' => $ilrd['country'] ?? null,
                    'is_header_bidding' => $ilrd['adsource_isheaderbidding'] ?? 0,

                    'create_time' => time(),
                    'update_time' => time(),
                ];

                // 8. 发放奖励
                $this->grantReward($user, $logData['ecpm'], $rewardName, $extraData);

                // 10. 保存到数据库
                $rewardLogRep->save($logData);
            });

            // 任务奖励
            $day_count = Db::name('reward_log')->whereDay('create_time')->count();
            $day_task_number = get_config('app.day_task_number');
            $reward_coins = get_config('app.reward_coins');
            if($day_count == $day_task_number){
                $userRep = app()->make(UserRepository::class);
                $userRep->setInc([
                    'id' => $userId, //cc_reward_log
                ], 'balance', 'inc', (int)$reward_coins);
            }

            Log::info("✅ 用户 {$userId} 成功获得奖励: {$rewardAmount} {$rewardName}, trans_id: {$transId}");
            return json(['code' => 0, 'msg' => 'success']);
        } catch (\Exception $e) {
            Log::error('TakuAd 回调异常: ' . $e->getMessage(), [
                'trace' => $e->getTraceAsString(),
                'data' => $data
            ]);
            return json(['code' => 500, 'msg' => '服务器错误']);
        }
    }

    /**
     * 验证签名（HMAC-SHA256）
     * 参考：https://help.takuad.com/docs/msbnkj#%E7%AD%BE%E5%90%8D%E8%A7%84%E5%88%99%E8%AF%B4%E6%98%8E
     * @param $data
     * @return bool
     */
    private function verifySign($data, $platform): bool
    {
        $ios_secKey = 'j4dy29trmx0xpg2kqxri8nxlou5vc2mb';
        $android_secKey = get_config('app.taku_secret');
        // 获取配置的 secret key（即 sec_key）
        $secKey = $platform == 1 ? $android_secKey : $ios_secKey; // 这个就是文档里的 sec_key
        $providedSign = strtolower($data['sign'] ?? ''); // 官方 MD5 通常是小写

        // 必须字段（按官方公式顺序不一定重要，但 key=value&... 形式要对）
        $requiredKeys = ['trans_id', 'placement_id', 'adsource_id', 'reward_amount', 'reward_name', 'ilrd'];
        foreach ($requiredKeys as $key) {
            if (!isset($data[$key])) {
                Log::error("缺少签名所需字段: {$key}");
                return false;
            }
        }

        // 构造 query string: key=value&...
        $params = [];
        $params['trans_id'] = $data['trans_id'];
        $params['placement_id'] = $data['placement_id'];
        $params['adsource_id'] = $data['adsource_id'];
        $params['reward_amount'] = $data['reward_amount'];
        $params['reward_name'] = $data['reward_name'];
        $params['sec_key'] = $secKey;           // ⚠️ 注意：sec_key 是 secret，要加进去
        $params['ilrd'] = $data['ilrd'];     // ⚠️ ilrd 是原始字符串，不要 JSON 解码！

        // 拼接成 trans_id=xxx&placement_id=yyy&...&ilrd={...}
        $str = '';
        foreach ($params as $k => $v) {
            $str .= $k . '=' . $v . '&';
        }
        $str = rtrim($str, '&'); // 去掉末尾多余的 &

        // 使用 MD5 计算签名
        $localSign = md5($str);

        // 对比（使用 hash_equals 防止时序攻击）
        return hash_equals($localSign, $providedSign);
    }

    /**
     * 发放奖励
     *
     * @param $user
     * @param $amount
     * @param $name
     * @param $extraData
     * @return void
     */
    private function grantReward($user, $amount, $name, $extraData = null): void
    {
        // 金币转换比例（可选）
        $rate = get_config('app.rate');

        // 收益分成比例（可选）
        $incomeRate = get_config('app.income_rate');

        // 确保 balance 是字符串
        $originalBalance = $user->balance;
        // 确保 amount 是字符串
        $addAmount = $amount * $rate * ($incomeRate / 100);

        if($addAmount <= 0) {
            return;
        }

        try {
            $newBalance = bcadd((string)$originalBalance, (string)$addAmount, 4);
        } catch (\Exception $e) {
            Log::error('余额计算失败: ' . $e->getMessage(), [
                'originalBalance' => $originalBalance,
                'addAmount' => $addAmount
            ]);
            throw new \RuntimeException('余额计算失败');
        }

        $user->balance = $newBalance;

        // 启用事务（如果 ORM 支持）
        try {
            $user->save();
        } catch (\Exception $e) {
            Log::error('用户余额更新失败: ' . $e->getMessage());
            throw $e;
        }
    }
}